﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web.UI.WebControls;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Globalization;

using AjaxControlToolkit;
using BizElements.Core;
using BizElements.BusinessLayer;

namespace BizElements.Web
{
    /// <summary>Base class for user control that display and edit business object which implement <see cref="BizElements.BusinessLayer.IBusinessObject"/> IDataObject.</summary>
    public abstract class DataObjectUserControlBase<T> : UserControl, IDataObjectUI
        where T : class, IDataObject, new()
    {
        #region CTor.

        /// <summary>Initializes a new instance of BusinessObjectUserControlBase class.</summary>
        public DataObjectUserControlBase()
        {
            this.Bindings = new EntityBindingContext();
        }

        #endregion

        #region Standard sub-controls: decorative elements, buttons, containers, tab...

        /// <summary>Form header or label control that contains edited object name, ie. default property value. Hidden if <see cref="HeaderAndFooterHidden"/> property is set to <b>true</b>.</summary>
        protected ITextControl HeaderControl { get; set; }

        /// <summary>Array of controls which are to be hidden if <see cref="HeaderAndFooterHidden"/> property is set to <b>true</b>.</summary>
        protected Control[] HeaderAndFooterDecorativeControls { get; set; }

        /// <summary>A control, often a TD or DIV element, that contains all controls bound to fields.</summary>
        protected HtmlControl FieldControlsContainer { get; set; }

        /// <summary>Control, often a TR or DIV element, that contains save, deactivate, cancel and other custom command buttons. Hidden if <see cref="ButtonsHidden"/> property is set to <b>true</b>.</summary>
        protected Control CommandButtonsContainer { get; set; }

        #endregion

        #region Bindings.

        /// <summary>Field, parent and children bindings.</summary>
        protected IEntityBindingContext Bindings { get; private set; }

        /// <summary>Initializes field, parent and children bindings.</summary>
        protected abstract void InitBindings();

        #endregion

        #region Init, Load, PreRender.

        /// <summary>Initializes field bindings.</summary>
        protected override void OnInit(EventArgs e)
        {
            InitBindings();
        }

        /// <summary>Displays data and captions, registers search providers, checks security permissions, enables/disables controls.</summary>
        protected override void OnLoad(EventArgs e)
        {
            // Null can only occur during design time. In runtime, owner control/page always initialize this property.
            if (this.BizObject == null)
                return;

            if (!IsPostBack)
            {
                IActor user = GetActor();
                CheckFunctions(user);
                FillAllListControls();
                DisplayHeader(user);
            }
            else
            {
                // Esnure that read-only flag is honored after every post-back.
                if (this.ReadOnly)
                    this.Bindings.FieldBindings.PropagateAccessMode(FieldAccessMode.ReadOnly);
            }

            RemoveRequiredFieldIndicatorsIfInReadOnlyMode();
            RegisterAllParentSearchProviders();
            SetNewAndDetailsParentCaptions();
        }

        /// <summary>Hides decorattive controls if <see cref="HeaderAndFooterHidden"/> is set to <b>true</b>.</summary>
        protected override void OnPreRender(EventArgs e)
        {
            if (this.HeaderAndFooterHidden)
                HideHeaderAndFooter();
        }

        /// <summary>Hides decorative elements, such as header and footer. Typically used when the user control is embeded in another user control.</summary>
        protected virtual void HideHeaderAndFooter()
        {
            if (this.HeaderAndFooterDecorativeControls != null)
            {
                foreach (Control ctl in this.HeaderAndFooterDecorativeControls)
                    ctl.Visible = false;
            }

            this.FieldControlsContainer.Style["border-style"] = "none";
        }

        #endregion

        #region CheckFunctions.

        /// <summary>Implements vertical (action privileges) and horizontal (record-base) security. Disables functions which the user is not authorized to use.
        /// Override to add security checks for custom actions.</summary>
        protected virtual void CheckFunctions(IActor user)
        {
            // Bound controls automatically switch read-only mode if object's IsReadOnly property is set to true.
            if (this.ReadOnly)
                this.BizObject.IsReadOnly = true;

            if (this.ButtonsHidden)
            {
                // If all buttons are hidden then it's not necessary to check priviliges. This may enhance performance. Typically used when the UC is embeded in another UC.
                if (this.CommandButtonsContainer != null)
                    this.CommandButtonsContainer.Visible = false;
            }

            // Check privileges to create and edit parent entities. Possible only if default concrete BL class exists for parent table.
            foreach (IParentBinding parentBinding in this.Bindings.ParentBindings)
            {
                var parentMeta = parentBinding.TryGetBusinessObjectDescriptor();

                Control createNewParentControl = parentBinding.GetCreateNewParentControl(this);
                if (createNewParentControl != null)
                    createNewParentControl.Visible = (parentMeta != null) && user.HasPermission(parentMeta.Permissions.CreatePermission);

                Control openParentDetailsControl = parentBinding.GetOpenParentDetailsControl(this);
                if (openParentDetailsControl != null)
                    openParentDetailsControl.Visible = (parentMeta != null) && user.HasPermission(parentMeta.Permissions.UserInterfacePermission) && user.HasPermission(parentMeta.Permissions.ReadPermission);
            }
        }

        #endregion

        #region Public interface.

        T bizObject;

        /// <summary>Gets or sets the object displayed/edited by current control.</summary>
        public virtual T BizObject
        {
            get { return this.bizObject; }
            set { this.bizObject = value; }
        }

        IDataObject IDataObjectUI.BizObject
        {
            get { return this.BizObject; }
            set
            {
                if (value != null)
                    this.BizObject = (T)value;
                else
                    this.bizObject = null;
            }
        }

        /// <summary>Indicates whether the component is in read-only mode. The property may be set in design time (ASPX/ASCX file) or during the Init stage of the owning page. Has no effect after Init phase.</summary>
        public bool ReadOnly { get; set; }

        /// <summary>Specifies whether the button/command container (typically TR or DIV element) is visible. Typically set to <b>false</b> when the control is embeded within another user control.</summary>
        public bool ButtonsHidden { get; set; }

        /// <summary>Specifies whether the decorative elements (header and footer) are visible. Typically set to <b>false</b> when the control is embeded within another user control.</summary>
        public bool HeaderAndFooterHidden { get; set; }

        /// <summary>Occurs when the user requests to open a dedicated form to edit an existing parent entity.</summary>
        public event EventHandler<EventArgs<WebFormContext>> OpenParent;

        /// <summary>Occurs when the user requests to open a dedicated form to create a new parent entity.</summary>
        public event EventHandler<EventArgs<WebFormContext>> NewParent;

        /// <summary>Refreshes/redraws the user control.</summary>
        public virtual void Refresh()
        {
            IActor user = GetActor();
            CheckFunctions(user);
            DisplayHeader(user);
        }

        /// <summary>Parses and validates business object and displays found errors.</summary>
        public virtual bool PopulateAndValidateBusinessObject()
        {
            return BindingsUtil.PopulateAndValidateBusinessObject(this.BizObject, this.Bindings.FieldBindings);
        }

        /// <summary>Changes business object access mode and propagates it to all bound controls.</summary>
        /// <param name="accessMode">Access mode.</param>
        /// <param name="doRefresh">Indicates whether the control should automatically refresh itself. Set to <b>false</b> if you will call <see cref="Refresh"/> method manually.</param>
        public virtual void ForceAccessMode(FieldAccessMode accessMode, bool doRefresh)
        {
            this.Bindings.FieldBindings.PropagateAccessMode(accessMode);
            if (accessMode == FieldAccessMode.ReadOnly)
                this.ReadOnly = this.BizObject.IsReadOnly = true;                
            else if (accessMode == FieldAccessMode.Edit)
                this.ReadOnly = this.BizObject.IsReadOnly = false;

            if (doRefresh)
                Refresh();
        }

        #endregion

        #region Header/root entity fields.

        /// <summary>Displays header fields.</summary>
        protected virtual void DisplayHeader(IActor user)
        {
            this.Bindings.FieldBindings.DisplayBusinessObject(this.BizObject);
            SetHeaderCaption(user);
        }

        /// <summary>Sets header caption.</summary>
        protected virtual void SetHeaderCaption(IActor user)
        {
            // If caption is not set then try to get default object name. Business layer resources should contain an entry with resource key equal to table name.
            if (string.IsNullOrEmpty(this.HeaderControl.Text))
                this.HeaderControl.Text = this.BizObject.GetFieldCaption("");
        }

        /// <summary>Asterisks are not displayed in read-only mode.</summary>
        protected virtual void RemoveRequiredFieldIndicatorsIfInReadOnlyMode()
        {
            bool shouldRemoveRequiredFieldIndicators = this.ReadOnly;
            if (!shouldRemoveRequiredFieldIndicators)
                return;

            foreach (IFieldBinding fieldBinding in this.Bindings.FieldBindings.FieldBindings)
            {
                ITextControl label = fieldBinding.CaptionLabel as ITextControl;
                if (label != null)
                    label.Text = TextUtil.RemoveSuffix(label.Text, BindingsUtil.RequiredFieldIndicator);
            }
        }

        /// <summary>Binds property to specified edit/display and caption controls.</summary>
        /// <param name="dbField">Database field.</param>
        /// <param name="captions">An object that returns field captions/label texts.</param>
        /// <param name="fieldControl">Edit/display control.</param>
        /// <param name="captionLabel">Caption control.</param>
        /// <param name="satelliteFields">Optional satellite fields.</param>
        /// <returns>Bound field.</returns>
        protected IBindableField BindField(IDbColumn dbField, ICaptionProvider captions, IBoundControl fieldControl, ITextControl captionLabel, params string[] satelliteFields)
        {
            return BindingsUtil.BindField(this.Bindings.FieldBindings, dbField, captions, fieldControl, captionLabel, satelliteFields);
        }

        /// <summary>
        /// Binds property to specified edit/display and caption controls.
        /// </summary>
        /// <param name="dbField">Database field.</param>
        /// <param name="captions">An object that returns field captions/label texts.</param>
        /// <param name="fieldControl">Edit/display control.</param>
        /// <param name="captionLabel">Caption control.</param>
        /// <param name="satelliteFields">Optional satellite fields.</param>
        /// <returns>Bound field.</returns>
        protected IBindableField BindField(IDbColumn dbField, ICaptionProvider captions, BoundTextBox fieldControl, ITextControl captionLabel, params string[] satelliteFields)
        {
            return BindingsUtil.BindField(this.Bindings.FieldBindings, dbField, captions, fieldControl, captionLabel, satelliteFields);
        }

        /// <summary>
        /// Binds property to specified edit/display and caption controls.
        /// </summary>
        /// <param name="dbField">Database field.</param>
        /// <param name="isNullable">Whether the value is required or not.</param>
        /// <param name="captions">An object that returns field captions/label texts.</param>
        /// <param name="fieldControl">Edit/display control.</param>
        /// <param name="captionLabel">Caption control.</param>
        /// <param name="satelliteFields">Optional satellite fields.</param>
        /// <returns>Bound field.</returns>
        protected IBindableField BindField(IDbColumn dbField, bool isNullable, ICaptionProvider captions, IBoundControl fieldControl, ITextControl captionLabel, params string[] satelliteFields)
        {
            return BindingsUtil.BindField(this.Bindings.FieldBindings, dbField, isNullable, captions, fieldControl, captionLabel, satelliteFields);
        }

        /// <summary>
        /// Binds property to specified edit/display and caption controls.
        /// </summary>
        /// <param name="dbField">Database field.</param>
        /// <param name="isNullable">Whether the value is required or not.</param>
        /// <param name="captions">An object that returns field captions/label texts.</param>
        /// <param name="fieldControl">Edit/display control.</param>
        /// <param name="captionLabel">Caption control.</param>
        /// <param name="satelliteFields">Optional satellite fields.</param>
        /// <returns>Bound field.</returns>
        protected IBindableField BindField(IDbColumn dbField, bool isNullable, ICaptionProvider captions, BoundTextBox fieldControl, ITextControl captionLabel, params string[] satelliteFields)
        {
            return BindingsUtil.BindField(this.Bindings.FieldBindings, dbField, isNullable, captions, fieldControl, captionLabel, satelliteFields);
        }

        /// <summary>
        /// Binds property to specified edit/display and caption controls.
        /// </summary>
        /// <param name="property">Public property.</param>
        /// <param name="dataType">Type of data contained in the property.</param>
        /// <param name="isNullable">Whether the value is required or not.</param>
        /// <param name="captions">An object that returns field captions/label texts.</param>
        /// <param name="fieldControl">Edit/display control.</param>
        /// <param name="captionLabel">Caption control.</param>
        /// <param name="satelliteFields">Optional satellite fields.</param>
        /// <returns>Bound field.</returns>
        protected IBindableField BindField(string property, Type dataType, bool isNullable, ICaptionProvider captions, IBoundControl fieldControl, ITextControl captionLabel, params string[] satelliteFields)
        {
            return BindingsUtil.BindField(this.Bindings.FieldBindings, property, dataType, isNullable, captions, fieldControl, captionLabel, satelliteFields);
        }

        /// <summary>
        /// Binds property to specified edit/display and caption controls.
        /// </summary>
        /// <param name="property">Public property.</param>
        /// <param name="dataType">Type of data contained in the property.</param>
        /// <param name="isNullable">Whether the value is required or not.</param>
        /// <param name="captions">An object that returns field captions/label texts.</param>
        /// <param name="fieldControl">Edit/display control.</param>
        /// <param name="captionLabel">Caption control.</param>
        /// <param name="satelliteFields">Optional satellite fields.</param>
        /// <returns>Bound field.</returns>
        protected IBindableField BindField(string property, Type dataType, bool isNullable, ICaptionProvider captions, BoundTextBox fieldControl, ITextControl captionLabel, params string[] satelliteFields)
        {
            return BindingsUtil.BindField(this.Bindings.FieldBindings, property, dataType, isNullable, captions, fieldControl, captionLabel, satelliteFields);
        }

        #endregion

        #region Fire events.

        /// <summary>Fires OpenParent event. Typically handled/processed by parent page which acts as an application/redirections controller.</summary>
        protected virtual void FireOpenParent(WebFormContext parentFormContext)
        {
            if (this.OpenParent != null)
                OpenParent(this, new EventArgs<WebFormContext>(parentFormContext));
        }

        /// <summary>Fires NewParent event. Typically handled/processed by parent page which acts as an application/redirections controller.</summary>
        protected virtual void FireNewParent(WebFormContext parentFormContext)
        {
            if (this.NewParent != null)
                NewParent(this, new EventArgs<WebFormContext>(parentFormContext));
        }

        #endregion

        #region Parents.

        /// <summary>Binds codelists to all list controls (drop-down lists, radio buttons lists, listboxes)</summary>
        protected virtual void FillAllListControls()
        {
            foreach (IParentBinding parentBinding in this.Bindings.ParentBindings)
            {
                var listControl = parentBinding.GetListControl(this);
                var dataSource = parentBinding.GetCodeList(GetActor());
                if (listControl != null && dataSource != null)
                {
                    listControl.DataSource = dataSource;
                    listControl.DataValueField = dataSource.ValueMember;
                    listControl.DataTextField = dataSource.DisplayMember;
                    listControl.DataBind();
                    listControl.Items.Insert(0, new ListItem("", ""));
                }
            }
        }

        /// <summary>Ensures that all search engines builders are registered in the <see cref="SearchProviderRegistry"/> for all parent entities which are selected via lookups.</summary>
        protected virtual void RegisterAllParentSearchProviders()
        {
            foreach (IParentBinding parentBinding in this.Bindings.ParentBindings)
            {
                if (!string.IsNullOrEmpty(parentBinding.SeachProviderBuilderControlId))
                    RegisterParentSearch(parentBinding.ForeignKeyPropertyName);
            }
        }

        /// <summary>Ensures that a global (used by all users and threads) search engine build object is registered in the <see cref="SearchProviderRegistry"/> for the specified FK property. Only single field FKs are supported.</summary>
        protected virtual void RegisterParentSearch(string foreignKeyPropertyName)
        {
            var binding = ArrayUtil.Find<IParentBinding>(this.Bindings.ParentBindings, (bind) => (bind.ForeignKeyPropertyName == foreignKeyPropertyName));
            if (binding == null)
                throw new NotImplementedException("No parent binding defined for property " + foreignKeyPropertyName);

            var lookup = binding.GetLookupControl(this);
            if (lookup == null)
                return;

            var builder = binding.NewSearchEngineBuilder();
            var searchProviderBuilder = binding.GetSearchProviderBuilderComponent(this);
            searchProviderBuilder.EngineBuilder = builder;
            bool isPopup = lookup is ISearchPageClient;
            var parentTable = binding.GetParentTable();
            var meta = binding.GetDataObjectDescriptor();
            
            IDbColumn defaultField = parentTable.Columns.GetByPropertyName(meta.Fields.DefaultPropertyName);
            string defaultFieldColumnName;
            if (defaultField != null)
                defaultFieldColumnName = defaultField.ColumnName;
            else
                // Bind anything.
                defaultFieldColumnName = builder.CreateSearchEngine(GetActor()).DisplayFields[0].BindingName;

            ISearchPageClient popupLookup = lookup as ISearchPageClient;
            if (popupLookup != null)
            {
                searchProviderBuilder.RegisterForClient(UserSession.UiCulture, popupLookup, true,
                    new string[] { parentTable.PrimaryKey[0].ColumnName, defaultFieldColumnName });
            }
            else
            {
                searchProviderBuilder.RegisterForClient(UserSession.UiCulture, lookup, false,
                new string[] { parentTable.PrimaryKey[0].ColumnName, defaultFieldColumnName });
            }
        }

        /// <summary>Sets caption for new parent and parent details controls if empty.</summary>
        private void SetNewAndDetailsParentCaptions()
        {
            foreach (IParentBinding parentBinding in this.Bindings.ParentBindings)
            {
                if (!string.IsNullOrEmpty(parentBinding.CreateNewParentControlId))
                    SetCreateNewParentControlCaptions(parentBinding.CreateNewParentControlId);

                if (!string.IsNullOrEmpty(parentBinding.OpenParentDetailsControlId))
                    SetOpenParentDetailsControlCaptions(parentBinding.OpenParentDetailsControlId);
            }
        }

        /// <summary>Sets caption for new parent control if empty.</summary>
        private void SetCreateNewParentControlCaptions(string createNewParentControlId)
        {
            WebControl newParentCtl = WebUtil.RecursivelyFindControl(this, createNewParentControlId) as WebControl;
            if (newParentCtl != null)
            {
                if (string.IsNullOrEmpty(newParentCtl.ToolTip))
                    newParentCtl.ToolTip = UiMessages.NewParentToolTip;

                IButtonControl newParentButton = newParentCtl as IButtonControl;
                if (newParentButton != null && string.IsNullOrEmpty(newParentButton.Text))
                    newParentButton.Text = UiMessages.NewParentText;

                TemplateButton btnWithConfirmation = newParentCtl as TemplateButton;
                if (btnWithConfirmation != null && string.IsNullOrEmpty(btnWithConfirmation.ConfirmationMessage))
                    btnWithConfirmation.ConfirmationMessage = UiMessages.NewParentConfirmationMessage;
            }
        }

        /// <summary>Sets caption for open parent details control if empty.</summary>
        private void SetOpenParentDetailsControlCaptions(string openParentDetailsControlId)
        {
            WebControl openParentCtl = WebUtil.RecursivelyFindControl(this, openParentDetailsControlId) as WebControl;
            if (openParentCtl != null)
            {
                if (string.IsNullOrEmpty(openParentCtl.ToolTip))
                    openParentCtl.ToolTip = UiMessages.ParentDetailsToolTip;

                IButtonControl openParentButton = openParentCtl as IButtonControl;
                if (openParentCtl != null && !string.IsNullOrEmpty(openParentButton.Text))
                    openParentButton.Text = UiMessages.ParentDetailsText;

                TemplateButton btnWithConfirmation = openParentCtl as TemplateButton;
                if (btnWithConfirmation != null && string.IsNullOrEmpty(btnWithConfirmation.ConfirmationMessage))
                    btnWithConfirmation.ConfirmationMessage = UiMessages.ParentDetailsConfirmationMessage;
            }
        }

        /// <summary>Fires <see cref="OpenParent"/> event. URL will be passed as event's <b>CommandArgument</b>.
        /// Owner page that hosts the user control decides how the specified URL is openned.</summary>
        protected virtual void HandleOpenParentDetailsCommand(object button, CommandEventArgs commandArgsWithForeignKeyPropertyName)
        {
            string fkPropertyName = Convert.ToString(commandArgsWithForeignKeyPropertyName.CommandArgument);
            OpenParentDetails(fkPropertyName);
        }

        /// <summary>Fires <see cref="NewParent"/> event. URL will be passed as event's <b>CommandArgument</b>.
        /// Owner page that hosts the user control decides how the specified URL is openned.</summary>
        protected virtual void HandleCreateNewParentCommand(object button, CommandEventArgs commandArgsWithForeignKeyPropertyName)
        {
            string fkPropertyName = Convert.ToString(commandArgsWithForeignKeyPropertyName.CommandArgument);
            CreateNewParent(fkPropertyName);
        }

        private void OpenParentDetails(string foreignKeyPropertyName)
        {
            IParentBinding bindingInfo = ArrayUtil.Find<IParentBinding>(this.Bindings.ParentBindings, (bind) => (bind.ForeignKeyPropertyName == foreignKeyPropertyName));
            if (bindingInfo == null)
                throw new InvalidOperationException("No binding defined for parent object via property " + foreignKeyPropertyName);

            string selectedValue = bindingInfo.GetSelectedValue(this);
            if (string.IsNullOrEmpty(selectedValue))
                return;

            int? numericId = NumberParser.ParseInt(selectedValue);
            if (numericId.HasValue && numericId.Value <= 0)
                return;

            var parentTable = bindingInfo.GetParentTable();
            string parentPkPropertyName = parentTable.PrimaryKey[0].PropertyName;
            string parentDefaultPropertyName = bindingInfo.GetDataObjectDescriptor().Fields.DefaultPropertyName;

            WebFormContext parentForm = new WebFormContext(bindingInfo.ParentFormUrl, selectedValue);
            // Parent.Id --> Child.ParentId
            parentForm.ReturnPropertyMap.Add(parentTable.PrimaryKey[0].PropertyName, bindingInfo.ForeignKeyPropertyName);
            var lookup = bindingInfo.GetLookupControl(this);
            if (lookup != null)
            {
                // Parent.EmployeeID --> LookupControl.SelectedValue
                parentForm.PropertyToControlMap.Add(parentPkPropertyName, lookup.SelectedValueControl.ClientID);
                // Parent.DefaultTextProperty --> LookupControl.Text
                parentForm.PropertyToControlMap.Add(parentDefaultPropertyName, lookup.TextControl.ClientID);
            }

            var ddlOrRadio = bindingInfo.GetListControl(this);
            if (ddlOrRadio != null)
            {
                // Parent.EmployeeID --> BoundControl.SelectedValue
                parentForm.PropertyToControlMap.Add(parentPkPropertyName, ((Control)ddlOrRadio).ClientID);
            }

            FireOpenParent(parentForm);
        }

        private void CreateNewParent(string foreignKeyPropertyName)
        {
            IParentBinding bindingInfo = ArrayUtil.Find<IParentBinding>(this.Bindings.ParentBindings, (bind) => (bind.ForeignKeyPropertyName == foreignKeyPropertyName));
            if (bindingInfo == null)
                throw new InvalidOperationException("No binding defined for parent object via property " + foreignKeyPropertyName);

            var parentTable = bindingInfo.GetParentTable();
            string parentPkPropertyName = parentTable.PrimaryKey[0].PropertyName;
            string parentDefaultPropertyName = bindingInfo.GetDataObjectDescriptor().Fields.DefaultPropertyName;

            WebFormContext parentForm = new WebFormContext(bindingInfo.ParentFormUrl);
            // Parent.Id --> Child.ParentId
            parentForm.ReturnPropertyMap.Add(parentTable.PrimaryKey[0].PropertyName, bindingInfo.ForeignKeyPropertyName);
            var lookup = bindingInfo.GetLookupControl(this);
            if (lookup != null)
            {
                // Parent.EmployeeID --> LookupControl.SelectedValue
                parentForm.PropertyToControlMap.Add(parentPkPropertyName, lookup.SelectedValueControl.ClientID);
                // Parent.DefaultTextProperty --> LookupControl.Text
                parentForm.PropertyToControlMap.Add(parentDefaultPropertyName, lookup.TextControl.ClientID);
            }

            var ddlOrRadio = bindingInfo.GetListControl(this);
            if (ddlOrRadio != null)
            {
                // Parent.EmployeeID --> BoundControl.SelectedValue
                parentForm.PropertyToControlMap.Add(parentPkPropertyName, ((Control)ddlOrRadio).ClientID);
            }

            FireNewParent(parentForm);
        }

        #endregion

        #region Utility.

        /// <summary>Gets current user. Prefer using this method in derived controls instead of UserSession.Actor property.</summary>
        protected virtual IActor GetActor()
        {
            return UserSession.Actor;
        }

        /// <summary>Makes a read-only collection editor.</summary>
        private static void MakeReadOnlyChildren(ICollectionEditor childrenEditor)
        {
            childrenEditor.ReadOnly = true;
            childrenEditor.HideAllButtons();
            childrenEditor.AlwaysEditBoundFields = false;
        }

        /// <summary>Gets control bound to the specified field.</summary>
        private Control GetControlBoundToField(string propertyName)
        {
            var binding = ArrayUtil.Find<IFieldBinding>(this.Bindings.FieldBindings.FieldBindings, (bind) => (bind.Field.BindingName == propertyName));
            return (binding != null) ? (Control)binding.BoundControl : null;
        }

        #endregion
    }  
}
